package jastgen.nodes;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static java.util.Arrays.asList;
import static java.util.Collections.*;
import static jastgen.util.Util.*;

public class Type {

	/* 以下のコンテナをインスタンス変数にした場合、
	 * コンストラクタでunmodifiableにする。
	 */
	private static final List<String>
	containerNames = asList(new String[] {
			"Collection",
			"List",
			"Map",
			"Set",
			"SortedMap",
			"SortedSet"});

	/* これらのコンテナクラスの型パラメータがノードクラスの場合、
	 * そのフィールドの要素も辿る
	 *
	 * MapとSortedMapはノードクラスのコンストラクタで自動的にunmodifiableにするが、
	 * このVisitorでは自動的に辿りはしない。
	 * 辿りたい場合は、MapやSortedMapをフィールドとして含むノードクラスのin*を自分でオーバーライドすること。
	 *
	 * CollectionでListになるのが嫌なら、
	 * 次のコンパイルパスで好きなコンテナに置換する
	 */
	private static final Map<String, String> containerI2C;
	static {
		final TreeMap<String, String> i2c = new TreeMap<String, String>();
		i2c.put("Collection", "ArrayList");
		i2c.put("List",       "ArrayList");
		i2c.put("Set",        "HashSet");
		i2c.put("SortedSet",  "TreeSet");
		containerI2C = unmodifiableSortedMap(i2c);
	}

	private final String name;
	private final List<Type> params;

	public Type(
			final String name)
	{
		this.name = name;
		this.params = emptyList();
	}

	public Type(
			final String name,
			final List<Type> params)
	{
		this.name = name;
		this.params = params;
	}

	public String getName() {
		return name;
	}

	public List<Type> getParams() {
		return params;
	}

	private boolean hasJustOneParam() {
		return params.size() == 1;
	}

	public Type getOnlyParam() {
		if(params.size() == 1)
			return params.get(0);
		throw new RuntimeException(
			"Check number of params before extraction.");
	}

	public String toString() {
		return name + paramsToString();
	}

	public String toStringQs() {
		return name + paramsToStringQ();
	}

	public String paramsToString() {
		return paramsToString(params);
	}

	public String paramsToStringQ() {
		return paramsToString(toQs(params));
	}

	public String getTypeParamsString() {
		final String s = paramsToString();
		if(s.isEmpty())
			return "";
		return s + " ";
	}

	private List<String> toQs(
			final List<Type> ts)
	{
		final List<String> xs = new ArrayList<String>(ts.size());
		for(final Type t : ts)
			xs.add("?");
		return xs;
	}

	private <T> String paramsToString(
			final List<T> params)
	{
		if(params.isEmpty())
			return "";
		return "<" + csep(params) + ">";
	}

	private static <T> String csep(
			final List<T> es)
	{
		String s = "";
		boolean is1st = true;
		for(final T e : es) {
			if(!is1st) s += ", ";
			is1st = false;
			s += e.toString();
		}
		return s;
	}

	public boolean maybeContainerToVisit() {
		return isVisitableContainer() && hasJustOneParam();
	}

	private boolean isVisitableContainer() {
		for(final String p : containerI2C.keySet()) {
			if(p.equals(name))
				return true;
		}
		return false;
	}

	public String getContainerImplClassName() {
		final String cn = containerI2C.get(name);
		if(cn == null)
			throw new RuntimeException(
				"'" + name + "' is not container.");
		return cn;
	}

	public boolean canbeUnmodifiable() {
		for(final String cn : containerNames)
			if(cn.equals(name))
				return true;
		return false;
	}

	@Override
	public boolean equals(final Object rhs) {
		if(this == rhs)
			return true;
		if(rhs instanceof Type)
			return equalsImpl((Type)rhs);
		return false;
	}

	private boolean equalsImpl(
			final Type rhs)
	{
		if(!eq(name, rhs.name))
			return false;
		if(!eq(params, rhs.params))
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		int hash = 17;
		hash += hash(name);
		hash *= 31;
		hash += hash(params);
		return hash;
	}
}
